package com.googlecode.perfectable.visuable.visual;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Optional;

import javax.annotation.Nullable;

import com.google.common.collect.Iterables;
import com.googlecode.perfectable.eventable.routing.Sink;
import com.googlecode.perfectable.introspection.Introspection;
import com.googlecode.perfectable.introspection.Methods;
import com.googlecode.perfectable.prosperable.message.ServerMessage;
import com.googlecode.perfectable.visuable.message.ValueChangeMessage;
import com.googlecode.perfectable.visuable.message.server.VisualAppendMessage;

public class NotifyingVisualProxy implements InvocationHandler {
	
	private final Sink<ServerMessage> sink;
	private final Visual wrapped;
	
	public static Visual create(Visual wrapped, Sink<ServerMessage> sink) {
		final Class<? extends Visual> visualClass = wrapped.getClass();
		ClassLoader classLoader = visualClass.getClassLoader();
		Class<?>[] interfaces = Iterables.toArray(Introspection.of(visualClass).interfaces(), Class.class);
		InvocationHandler handler = new NotifyingVisualProxy(wrapped, sink);
		Visual proxy = (Visual) Proxy.newProxyInstance(classLoader, interfaces, handler);
		if(proxy == null) {
			throw new RuntimeException();
		}
		return proxy;
	}
	
	private NotifyingVisualProxy(Visual wrapped, Sink<ServerMessage> sink) {
		this.wrapped = wrapped;
		this.sink = sink;
	}
	
	public void append(Visual child) {
		if(!(this.wrapped instanceof ParentVisual)) {
			throw new UnsupportedOperationException();
		}
		((ParentVisual) this.wrapped).append(child);
		this.sink.receive(VisualAppendMessage.create((ParentVisual) this.wrapped, child));
	}
	
	public void setValue(String newValue) {
		if(!(this.wrapped instanceof ValueHoldingVisual)) {
			throw new UnsupportedOperationException();
		}
		((ValueHoldingVisual) this.wrapped).setValue(newValue);
		this.sink.receive(ValueChangeMessage.create((ValueHoldingVisual) this.wrapped, newValue));
	}
	
	public void setText(String newText) {
		if(!(this.wrapped instanceof TextualVisual)) {
			throw new UnsupportedOperationException();
		}
		((TextualVisual) this.wrapped).setText(newText);
		// MARK
		// this.sink.receive(TextChangeMessage.create((TextualVisual) this.wrapped, newText));
	}
	
	public void appendText(String addition) {
		if(!(this.wrapped instanceof TextualVisual)) {
			throw new UnsupportedOperationException();
		}
		((TextualVisual) this.wrapped).appendText(addition);
		// MARK
		// this.sink.receive(TextAppendMessage.create((TextualVisual) this.wrapped, newText));
	}
	
	@Nullable
	@Override
	public Object invoke(@Nullable Object proxy, @Nullable Method method, @Nullable Object[] parameters) throws Throwable {
		if(method == null) {
			throw new IllegalArgumentException();
		}
		Optional<Method> invokerMethod = Methods.similar(NotifyingVisualProxy.class, method);
		if(invokerMethod.isPresent()) {
			return invokerMethod.get().invoke(this, parameters);
		}
		return method.invoke(this.wrapped, parameters);
	}
	
}
